package com.dt.zbcy.provider.core.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Auther: liuwenxiang
 * @Date: 2018/12/20 15:06
 * @Description:
 */
//@Configuration
public class ShiroConfiguration {

    private final static Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);



    /*@Value("${spring.redis.shiro.host}")
    private String host;
    @Value("${spring.redis.shiro.port}")
    private int port;
    @Value("${spring.redis.shiro.timeout}")
    private int timeout;
    @Value("${spring.redis.shiro.password}")
    private String password;*/






    // No SecurityManager accessible to the calling code, either bound to the
    // org.apache.shiro.util.ThreadContext or as a vm static singleton. This is
    // an invalid application configuration.
    @Bean
    public FilterRegistrationBean delegatingFilterProxy() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }

    @Bean(name = "shiroLoginFilter")
    public ShiroLoginFilter shiroLoginFilter() {
        ShiroLoginFilter shiroLoginFilter = new ShiroLoginFilter();
        return shiroLoginFilter;
    }

    /**Apache Shiro 核心通过 Filter 来实现，就好像SpringMvc 通过DispachServlet 来主控制一样。
     * 1.shiro实现认证，授权，会话管理，加密是通过一些列的过滤器处理了，然后交给securityManager，securityManager将认证，授权交给realm处理，将会话交给sessionManager处理。
     * 2.shiroFilterFactoryBean是shiro过滤器的入口
     * 3.shiroFilter维护的三个模块：securityManager，自定义filters，filterChainDefinitionManager。
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {

        logger.info("ShiroConfiguration.shiroFilterFactoryBean:开始shiro核心过滤器工厂");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager());

        // 退出登录过滤器
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setRedirectUrl("/ajax_login");

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/ajax_login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizedUrl");

        //添加自定义的过滤器
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("loginfilter", shiroLoginFilter());
        /*filters.put("authc", new StatelessAuthcFilter());
        filters.put("kickout", kickoutSessionControlFilter());*/
        shiroFilterFactoryBean.setFilters(filters);

        // 注意过滤器配置顺序 不能颠倒
        // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了，登出后跳转配置的loginUrl
        Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>();
        // 配置shiro默认登录界面地址，前后端分离中登录界面跳转应由前端路由控制，后台仅返回json数据 */
        filterChainDefinitionManager.put("/api/v1/zbcy_service/admin/ajax_login", "anon");// 用户为ROLE_USER
        filterChainDefinitionManager.put("/api/v1/zbcy_service/admin/login", "anon");// 用户为ROLE_USER
        filterChainDefinitionManager.put("/static/**", "anon");// 用户为ROLE_USER
        filterChainDefinitionManager.put("/logout", "anon");// 用户为ROLE_USER
        filterChainDefinitionManager.put("/getMenu", "anon");// 用户为ROLE_USER
        filterChainDefinitionManager.put("/**", "loginfilter");// shiro只有设置了拦截器才会拦截，如果没有设置，是可以不登陆访问的
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
        logger.info("ShiroConfiguration.shiroFilterFactoryBean:结束shiro核心过滤器工厂");
        return shiroFilterFactoryBean;
    }

    /**
     * SecurityManager，权限管理，这个类组合了登陆，登出，权限，session的处理，是个比较重要的类。
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(ehCacheManager());
        //securityManager.setCacheManager(cacheManager());// 自定义缓存实现 使用redis
        // securityManager.setRememberMeManager(rememberMeManager()); // 重写的
        return securityManager;
    }





    /**
     * ShiroRealm，这是个自定义的认证类，继承自AuthorizingRealm，
     * 负责用户的认证和权限的处理，可以参考JdbcRealm的实现。
     */
    @Bean(name = "shiroRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ShiroRealm shiroRealm() {
        ShiroRealm realm = new ShiroRealm();
        // realm.setCredentialsMatcher(hashedCredentialsMatcher());//暂时屏蔽掉加盐算法
        return realm;
    }


    // 自定义sessionManager
    @Bean
    public SessionManager sessionManager() {
        /**
         * sessionValidationScheduler:间隔多少时间检查，不配置是60分钟
         * sessionValidationSchedulerEnabled:是否开启 检测，默认开启
         * deleteInvalidSessions:是否删除无效的，默认也是开启
         **/
        // 当用户操作时，会更新最后一次修改时间，就又会重新算会话超时时间
        MySessionManager mySessionManager = new MySessionManager();
        // 1.为负数表示永不超时,将shiro的session的超时交由redis的超时来管理.当不设置负数，而是某个超时时间，用户在超时时间段内没有访问，就会删除redis会话，且用户会话未超时。但是当用户在这个时间段内访问了，就会更新最后访问时间，就不会超时。
        // 2.每次请求都检测redis的会话有没超时，当redis超时后,就代表该用户会话超时了,用户需要重新登录。
        // 3.当服务器宕机，浏览器cookie，redis会话未超时的情况下，重启服务器，用户无需重新登录便可继续访问。
        // 4.由于ajax请求Credentials：false,代表请求不带cookie。我们通过重写会话id后，通过请求头验证。此时当redis宕机，服务器就没有会话，而浏览器cookie没过期，当访问来了，redis就会新增一条会话，而这个会话是无用的会话，所以的redis的会话不能设置永不过期。
        // mySessionManager.setGlobalSessionTimeout(-1);
        // 这两个参数，shiro都有设置默认值
        // mySessionManager.setSessionValidationInterval(2*60*60*1000);//相隔多久检查一次session的有效性
        // mySessionManager.setGlobalSessionTimeout(6000);//session
        // 有效时间为半小时 （毫秒单位）
        // 是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true（会话到期，会删掉reids中的会话）
        // mySessionManager.setDeleteInvalidSessions(false);
        //mySessionManager.setSessionDAO(redisSessionDAO());

        return mySessionManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * <p>
     * 使用的是shiro-redis开源插件
     */
   /* @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }*/

    /**
     * cacheManager 缓存 redis实现
     * <p>
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
   /* @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }*/

    /*
     * EhCacheManager，缓存管理，用户登陆成功后，把用户信息和权限信息缓存起来，
     * 然后每次用户请求时，放入用户的session中，如果不设置这个bean，每个请求都会查询一次数据库。
     */

      @Bean(name = "ehCacheManager")
      @DependsOn("lifecycleBeanPostProcessor")
      public EhCacheManager ehCacheManager() {
          EhCacheManager ehcacheManager = new EhCacheManager();
          ehcacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
          return ehcacheManager;
      }


    /**
     * 配置shiro redisManager
     * <p>
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
  /*  @Bean
    public RedisManager redisManager() {
        System.out.println("配置文件中的redis" + host);
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(0);// 配置缓存过期时间 (0代表不过期)
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }
*/





    /**
     * LifecycleBeanPostProcessor，这是个DestructionAwareBeanPostProcessor的子类，
     * 负责org.apache.shiro.util.Initializable类型bean的生命周期的，初始化和销毁。
     * 主要是AuthorizingRealm类的子类，以及EhCacheManager类。
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


    /**
     * HashedCredentialsMatcher，这个类是为了对密码进行编码的， 防止密码在数据库里明码保存，当然在登陆认证的时候，
     * 这个类也负责对form里输入的密码进行编码。
     * 凭证匹配器 （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 ）
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:这里使用MD5算法;
        credentialsMatcher.setHashIterations(2);// 散列的次数，比如散列两次，相当于
        // md5(md5(""));
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }

    /**
     * 授权所用配置 DefaultAdvisorAutoProxyCreator是用来扫描上下文，寻找所有的Advistor(通知器），
     * 将这些Advisor应用到所有符合切入点的Bean中
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    /**
     * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; Controller才能使用@RequiresPermissions
     * AuthorizationAttributeSourceAdvisor，shiro里实现的Advisor类，
     * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor aASA = new AuthorizationAttributeSourceAdvisor();
        aASA.setSecurityManager(securityManager());
        return aASA;
    }


 /*1.LifecycleBeanPostProcessor，这是个DestructionAwareBeanPostProcessor的子类，负责org.apache.shiro.util.Initializable类型bean的生命周期的，初始化和销毁。主要是AuthorizingRealm类的子类，以及EhCacheManager类。
    2.HashedCredentialsMatcher，这个类是为了对密码进行编码的，防止密码在数据库里明码保存，当然在登陆认证的生活，这个类也负责对form里输入的密码进行编码。
    3.ShiroRealm，这是个自定义的认证类，继承自AuthorizingRealm，负责用户的认证和权限的处理，可以参考JdbcRealm的实现。
    4.EhCacheManager，缓存管理，用户登陆成功后，把用户信息和权限信息缓存起来，然后每次用户请求时，放入用户的session中，如果不设置这个bean，每个请求都会查询一次数据库。
    5.SecurityManager，权限管理，这个类组合了登陆，登出，权限，session的处理，是个比较重要的类。
    6.ShiroFilterFactoryBean，是个factorybean，为了生成ShiroFilter。它主要保持了三项数据，securityManager，filters，filterChainDefinitionManager。
    7.DefaultAdvisorAutoProxyCreator，Spring的一个bean，由Advisor决定对哪些类的方法进行AOP代理。
    8.AuthorizationAttributeSourceAdvisor，shiro里实现的Advisor类，内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。*/



}
